/**
  ******************************************************************************
  * @file           IEC104_link.c
  * @author         古么宁
  * @brief          IEC 104 链路层解析
  * @note           
  * <pre>
  * |<------------  APDU  -------------->|
  * | 0x68 + len(APDU) + ctrls[4] + ASUD |
  * |                    |<- len(APDU) ->|
  * |<------------APCI----------->|<ASUD>|
  * </pre>
  ******************************************************************************
  *
  * COPYRIGHT(c) 2019 GoodMorning
  *
  ******************************************************************************
  */
/* Includes ---------------------------------------------------*/
#include <string.h>

#include "iec10x.h"
#include "iec10x_link.h"
#include "iec10x_event.h"
#include "iec10x_debug.h"

#include "COT.h"

#if (ENABLE_IEC104_MODE)

/* Private types ------------------------------------------------------------*/

struct iec104pkt{
	uint8_t head    ;
	uint8_t len     ;
	uint8_t ctrls[4];
	uint8_t ASDU[1] ;
};

struct uctrl{
	#if (IEC_ENDIAN == IEC_LITTLE_ENDIAN)
		uint8_t res            : 2 ; // 如果是 u 格式帧，低 2 bit 恒为 1
		uint8_t STARTDTeffect  : 1 ; // STARTDT 生效
		uint8_t STARTDTconfirm : 1 ; // STARTDT 确认位
		uint8_t STOPDTeffect   : 1 ; // STOPDT 生效
		uint8_t STOPDTconfirm  : 1 ; // STOPDT 确认位
		uint8_t TESTFReffect   : 1 ; // TESTFR 生效
		uint8_t TESTFRconfirm  : 1 ; // TESTFR 确认位
	#else 
		uint8_t TESTFRconfirm  : 1 ; // TESTFR 确认位
		uint8_t TESTFReffect   : 1 ; // TESTFR 生效
		uint8_t STOPDTconfirm  : 1 ; // STOPDT 确认位
		uint8_t STOPDTeffect   : 1 ; // STOPDT 生效
		uint8_t STARTDTconfirm : 1 ; // STARTDT 确认位
		uint8_t STARTDTeffect  : 1 ; // STARTDT 生效
		uint8_t res            : 2 ; // 如果是 u 格式帧，低 2 bit 恒为 1
	#endif
};


/* Private macro ------------------------------------------------------------*/

#define PARSE_OK         0
#define PARSE_ERROR      1
#define PARSE_INCOMPLETE 2

/* Private variables --------------------------------------------------------*/

/* Global  variables --------------------------------------------------------*/

/* Private function prototypes ----------------------------------------------*/

/* Gorgeous Split-line ------------------------------------------------------*/


/**
  * @brief    总线接收 U 帧。
  * @note     控制域 & 0x03 == 0x03 为 U 帧，U 帧只包括 APCI（没有 ASUD），格式如下
  * <pre>
  * |      TESTFR      |      STOPDT      |     STARTDT      |1|1|
  * | confirm | effect | confirm | effect | confirm | effect |1|1|
  * |    8    |    7   |    6    |   5    |    4    |   3    |2|1|(bit)
  * 同一时刻 TESTFR ,STOPDT,STARTDT 只有一个功能是激活的。
  * </pre>
  * @param    bus      : 需要解析的总线结构体 
  * @param    ctrlval  : 控制域
  * @return   don't care
*/
static void iec104slv_uframe(iec10x_t * bus , uint8_t ctrlval)
{
	uint8_t ctrlack = 0x03;
	struct  uctrl * ack  = (struct uctrl *)&ctrlack ;

	if (ctrlval == 0x07) {
		/* 建立网络连接，启动链路 */
		iec104_disconnect(bus);
		ack->STARTDTconfirm = 1;
		bus->connected      = 1;
		bus->periodt        = iec_timestamp;
		bus->iec104.txnums         = 0;
		bus->iec104.txcheck        = 0;
		bus->iec104.rxnums         = 0;
		bus->iec104.rxcheck        = 0;
	}
	else
	if (!bus->connected) {
		/* 如果链路为断开状态，不回复 */
		return ;
	}
	else
	if (ctrlval == 0x13) {
		/* 停止链路 */
		ack->STOPDTconfirm = 1;
		bus->connected     = 0;
	}
	else 
	if (ctrlval == 0x43) {
		/* 测试帧 */
		ack->TESTFRconfirm = 1;
	}
	else
	if (ctrlval == 0x83){
		/* 测试帧回应，解锁总线操作（在 iec104_heartbeat() 中置位） */
		bus->busy &= ~IEC10x_HEARTBEAT;
		bus->onlinet = iec_timestamp;
		return ;
	}
	else {
		iec10x_err(bus,"cannot identity this U-control(0x%x).\r\n",ctrlval);
		return ;
	}

	/* 返回确认帧 */
	iec104_protocol_send(bus,NULL,0,ctrlack);
}



/**
  * @brief    总线接收 U 帧。
  * @note     控制域 & 0x03 == 0x03 为 U 帧，U 帧只包括 APCI（没有 ASUD），格式如下
  * <pre>
  * |      TESTFR      |      STOPDT      |     STARTDT      |1|1|
  * | confirm | effect | confirm | effect | confirm | effect |1|1|
  * |    8    |    7   |    6    |   5    |    4    |   3    |2|1|(bit)
  * 同一时刻 TESTFR ,STOPDT,STARTDT 只有一个功能是激活的。
  * </pre>
  * @param    bus      : 需要解析的总线结构体 
  * @param    ctrlval  : 控制域
  * @return   don't care
*/
static void iec104mst_uframe(iec10x_t * bus , uint8_t ctrlval)
{
	if (ctrlval == 0x0B) {
		/* 建立网络连接成功 */
		bus->connected      = 1;
		bus->busy           = 0;
		bus->onlinet        = iec_timestamp ;
		bus->periodt        = iec_timestamp - bus->data_period;
		bus->synct          = iec_timestamp - bus->sync_period;
		bus->iec104.txnums  = 0;
		bus->iec104.txcheck = 0;
		bus->iec104.rxnums  = 0;
		bus->iec104.rxcheck = 0;
		iec10x_msg(bus,"connected!\r\n");
	}
	else
	if (ctrlval == 0x23) {
		/* 停止链路 */
		bus->connected = 0;
		bus->onlinet = iec_timestamp;
	}
	else 
	if (ctrlval == 0x43) {
		/* 从机主动发送测试帧，返回确认帧 */
		iec104_protocol_send(bus,NULL,0,0x83);
		bus->onlinet = iec_timestamp;
	}
	else
	if (ctrlval == 0x83){
		/* 测试帧回应，解锁总线操作（在 iec104_heartbeat() 中置位） */
		bus->busy &= ~IEC10x_HEARTBEAT;
		bus->onlinet = iec_timestamp;
	}
	else  {
		iec10x_err(bus,"cannot identity this U-control(0x%x).\r\n",ctrlval);
		return ;
	}
}


/**
  * @brief    总线接收 S 帧。
  * @note     记录接收到的长帧，主站可以按频率发送 S 帧，比如接收 
  *           8 帧 I 帧回答一帧 S 帧，也可以要求接收 1 帧 I 帧就应答 1 帧 S 帧。
  * @param    bus    : 需要解析的总线结构体 
  * @param    ctrls  : 控制域
  * @return   don't care
*/
static void iec104slv_sframe(iec10x_t * bus ,const uint8_t * ctrls)
{
	uint16_t txcheck = ((ctrls[2]>>1)|(ctrls[3]<<7));

	/* 更新滑动窗口 */
	bus->iec104.txcheck = txcheck ;
}



static void iec104slv_iframe(iec10x_t * bus ,const uint8_t * data ,uint16_t len)
{
	uint16_t recvcheck = 0 , sendcheck = 0;
	struct iec104pkt * pkt  = (struct iec104pkt*)data;
	ASDUprofn_t asdupro ;
	recvcheck = (pkt->ctrls[0] >> 1)|(pkt->ctrls[1] << 7);
	sendcheck = (pkt->ctrls[2] >> 1)|(pkt->ctrls[3] << 7);
	if (recvcheck != bus->iec104.rxnums) {
		iec10x_err(bus,"I:recv num check error\r\n");
		iec104_disconnect(bus);
		return ;
	}
	else {
		bus->iec104.rxnums  = recvcheck + 1;
		bus->iec104.txcheck = sendcheck;
	}
	
	/* 查找对应类型的响应函数，响应函数仅能通过 iec101_type_listen() 添加，
	   在 iec104_protocol_init() 统一注册 */
	asdupro = ASDU_pro(pkt->ASDU[0]) ; 
	if (asdupro) {
		asdupro(bus, pkt->ASDU,pkt->len-4) ;
	}
}


static void iec104mst_iframe(iec10x_t * bus ,const uint8_t * data ,uint16_t len)
{
	uint16_t recvcheck = 0 , sendcheck = 0;
	struct iec104pkt * pkt  = (struct iec104pkt*)data;
	sendcheck = (pkt->ctrls[2] >> 1)|(pkt->ctrls[3] << 7);
	ASDUprofn_t asdupro ;

	recvcheck = (pkt->ctrls[0] >> 1)|(pkt->ctrls[1] << 7);
	if (recvcheck != bus->iec104.rxnums) {
		/* 对方发送序号错乱 */
		iec10x_err(bus,"I:recv num check error\r\n");
		iec104_disconnect(bus);
		return ;
	}
	else {
		bus->iec104.rxnums++;
		bus->onlinet = iec_timestamp;
		bus->iec104.txcheck = sendcheck;
	}

	if (recvcheck - bus->iec104.rxcheck > bus->iec104.w) {
		/* bus->iec104.rxcheck 为已确认回复了的帧，
		   接收到 w 帧 I格式报文后，发送 S 帧报文更新滑动窗口 */
		bus->iec104.rxcheck = bus->iec104.rxnums ;
		iec104_protocol_send(bus,0,0,1);
	}

	/* 查找对应类型的响应函数，响应函数仅能通过 iec101_type_listen() 添加，
	   在 iec104_protocol_init() 统一注册 */
	asdupro = ASDU_pro(pkt->ASDU[0]) ;
	if (asdupro) {
		asdupro(bus, pkt->ASDU,pkt->len-4) ;
	}
}



/**
  * @brief    总线对需要发送的 ASDU 数据进行打包，调用硬件发送
  * @param    bus       : 需要解析的总线结构体 
  * @param    ASDU      : ASDU 数据 
  * @param    ASDUlen   : ASDU 数据长度 
  * @param    lnkctrl   : ASDU 数据所需控制域
  * @return   don't care
*/
void iec104_protocol_send(iec10x_t * bus , uint8_t * ASDU ,int ASDUlen ,uint8_t ctrl)
{
	struct iec104pkt *txpkt = (struct iec104pkt*)bus->txbuf;
	uint16_t len = 6  ;
	txpkt->head = 0x68;
	txpkt->len  = 4   ;
	if (ASDU && ASDUlen) {  
		/* 发送可变长 I 帧需要更新发送序号 */
		txpkt->len += ASDUlen ;
		len += ASDUlen ;
		txpkt->ctrls[0] = (bus->iec104.txnums<<1) & 0x00fe;
		txpkt->ctrls[1] = (bus->iec104.txnums>>7) & 0x00ff;
		bus->iec104.txnums++;

		txpkt->ctrls[2] = (bus->iec104.rxnums<<1) & 0x00fe;
		txpkt->ctrls[3] = (bus->iec104.rxnums>>7) & 0x00ff;
		if (txpkt->ASDU != ASDU) {
			memcpy(txpkt->ASDU,ASDU,ASDUlen);
		}
	}
	else 
	if ((ctrl & 0x03) == 0x03 ){
		/* 发送 U 帧 */
		txpkt->ctrls[0] = ctrl;
		txpkt->ctrls[1] = 0 ;
		txpkt->ctrls[2] = 0 ;
		txpkt->ctrls[3] = 0 ;
	}
	else 
	if (ctrl & 0x01) {
		/* 发送 S 帧确认 */
		txpkt->ctrls[0] = ctrl;
		txpkt->ctrls[1] = 0 ;
		txpkt->ctrls[2] = (bus->iec104.rxnums << 1) & 0x0fe;
		txpkt->ctrls[3] = (bus->iec104.rxnums >> 7) & 0x0ff;
	}
	else {
		return ;
	}

	/* 报文解析打印 */
	IEC104_PACKET_INFO(bus,bus->txbuf,len,IEC_TX_PKT);

	/* 从硬件发送此包，并更新发送序号 */
	bus->haltx(bus->txbuf,len);
	return;
}



/**
  * @brief    总线解析一帧完整的包
  * @param    bus   : 需要解析的总线结构体 
  * @param    data  : 包数据 
  * @param    len   : 包数据长度
  * @return   don't care
*/
static void iec104_parse(iec10x_t * bus ,const uint8_t * data ,int pktlen)
{
	/* 报文解析打印 */
	IEC104_PACKET_INFO(bus,(uint8_t *)data,pktlen,IEC_RX_PKT);

	if (bus->mode & IEC_MASTER){
		/* 主站模式下响应包接收 */
		if ((data[2] & 0x03) == 0x03) { // u 帧
			iec104mst_uframe(bus,data[2]);
		}
		else
		if ((data[2] & 0x01) == 0x01) { // s 帧
			iec104slv_sframe(bus,&data[2]);
		}
		else {
			iec104mst_iframe(bus,data,pktlen);
		}
	}
	else  {
		/* 从站模式下响应包接收 */
		if ((data[2] & 0x03) == 0x03) { // u 帧
			iec104slv_uframe(bus,data[2]);
		}
		else
		if ((data[2] & 0x01) == 0x01) { // s 帧
			iec104slv_sframe(bus,&data[2]);
		}
		else {  
			iec104slv_iframe(bus,data,pktlen);
		}
	}
}

/**
  * @brief    总线接收数据并解析
  * @param    bus   : 需要解析的总线结构体 
  * @param    data  : 包数据 
  * @param    len   : 包数据长度
  * @return   don't care
*/
void iec104_protocol_recv(iec10x_t * bus ,const uint8_t * rxbuff ,int len)
{
	uint8_t * data = (uint8_t *)rxbuff;
	int pktlen , need ;
	bus->onlinet = iec_timestamp;

	/* 断包判断处理，上一次不完整的包存于 bus->rxbuf 中，长度为 bus->rxtail ，
	   把当前包 data 拼接至 bus->rxbuf 后面，如果包完整则解析，否则继续缓存 */
	while(bus->rxtail && len) {
		pktlen  = (bus->rxtail > 1) ? bus->rxbuf[1] : data[0];
		pktlen += 2 ;
		need   = pktlen - bus->rxtail ;
		if (len < need) {
			/* 包未完整，缓存等待下一包 */
			memcpy(&bus->rxbuf[bus->rxtail],data,len);
			bus->rxtail += len ;
			return ;
		}
		memcpy(&bus->rxbuf[bus->rxtail],data,need);
		data       += need ;
		len        -= need ;
		bus->rxtail = 0    ;
		iec104_parse(bus,bus->rxbuf,pktlen);
	}

	while(len) {
		if (data[0] != 0x68) {
			iec10x_warn(bus,"cannot found frame head!\n");
			return ;
		}
		else
		if (len < 2 || len < data[1] + 2) {
			memcpy(&bus->rxbuf[0],data,len);
			bus->rxtail = len ;
			return ;
		}

		pktlen = data[1] + 2;
		iec104_parse(bus,data,pktlen);
		len   -= pktlen ;
		data  += pktlen ;
	}
}



/**
  * @author   古么宁
  * @brief    断开连接
  * @param    bus : 对象总线
  * @return   
*/
void iec104_disconnect(iec10x_t * bus)
{
	bus->ctrling   = 0;
	bus->connected = 0;
	bus->busy      = 0;
	memset(&bus->operate,0,sizeof(struct iec_do));
	memset(&bus->ctrl   ,0,sizeof(struct iec_do));
	
	#if (ENABLE_FILE_TRANS)
		if (bus->interrupt) {
			bus->interrupt(bus);
			bus->interrupt = NULL;
		}
		memset(&bus->fs, 0, sizeof(struct iec_do));
	#endif

	iec10x_event_deinit(bus);
}

/**
  * @author   古么宁
  * @brief    发送重连接请求
  * @param    bus : 需要连接的总线
  * @return   
*/
void iec104mst_conncet(iec10x_t * bus)
{
	/* 发送启动链路报文 */
	iec104_protocol_send(bus,0,0,0x07);
	bus->iec104.rxcheck = 0; 
	bus->iec104.txcheck = 0;
	bus->iec104.txnums  = 0;
	bus->iec104.rxnums  = 0;
}


/**
  * @author   古么宁
  * @brief    发送链路测试心跳包
  * @param    bus : 对象总线
  * @return   
*/
void iec104_heartbeat(iec10x_t *bus)
{
	/* 发送测试链路报文并等待回应 */
	iec104_protocol_send(bus,0,0,0x43);
	bus->onlinet = iec_timestamp;
	bus->busy   |= IEC10x_HEARTBEAT;
}



#else /** #if (ENABLE_IEC104_MODE) */

void iec104_protocol_recv(iec10x_t * bus ,const uint8_t * data ,int len)
{
	iec10x_warn(bus,"iec104 is unable.\r\n"
		"please check \"iec_sys.h\" : \r\n"
		"ENABLE_IEC104_MASTER or ENABLE_IEC104_SLAVE \r\n");
}

#endif
